﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data.SQLite;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Windows.Forms;
using MalaCSharp;
using Tools.MAME;

namespace GameListEditor
{
    /// <summary> Main application form. </summary>
    public partial class FormMain : Form
    {
        ///-------------------------------------------------------------------------------------------------
        /// <summary> Values that represent FilterMode. </summary>
        ///-------------------------------------------------------------------------------------------------
        private enum FilterMode
        {
            Standard,
            Include,
            Exclude
        }

        // --------------------------------------------------------------------
        // Member Variables
        // --------------------------------------------------------------------
        #region Member Variables

        /// <summary> Application data manager.  </summary>
        private AppDataMgr m_appDataMgr = new AppDataMgr();

        /// <summary> Merged database.  </summary>
        private GameDatabase m_database = new GameDatabase();

        /// <summary> Thread helper.  </summary>
        private ThreadHelper m_threadHelper = new ThreadHelper();

        /// <summary>
        /// Setting storage manager.
        /// </summary>
        private SettingStorage m_settings = new SettingStorage();

        #endregion

        // --------------------------------------------------------------------
        // Constructor/Destructor
        // --------------------------------------------------------------------
        #region Constructor/Destructor

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Initializes a new instance of the <see cref="T:System.Windows.Forms.Form" /> class. </summary>
        ///-------------------------------------------------------------------------------------------------
        public FormMain()
        {
            InitializeComponent();

            LBL_FilterImage.Visible = false;

            m_database.StatusMessage += new EventHandler<GameDatabase.StatusMessageEventArgs>(database_StatusMessage);

            UpdateStatus(AppStatus.Idle);
            UpdateStatusBarWithCount();
        }

        #endregion

        #region Event Handlers

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Event handler. Called by database for status message events. </summary>
        /// <param name="sender"> Source of the event. </param>
        /// <param name="e">      Status message event information. </param>
        ///-------------------------------------------------------------------------------------------------
        private void database_StatusMessage(object sender, GameDatabase.StatusMessageEventArgs e)
        {
            try
            {
                UpdateStatusBar(e.Message);
            }
            catch
            { }
        }

        #endregion

        // --------------------------------------------------------------------
        // Control Event Handlers
        // --------------------------------------------------------------------
        #region Control Event Handlers

        ///-------------------------------------------------------------------------------------------------
        /// <summary> mnuView_DropDownOpening event handler. </summary>
        /// <param name="sender"> Sender. </param>
        /// <param name="e">      Arguments. </param>
        ///-------------------------------------------------------------------------------------------------
        private void mnuView_DropDownOpening(object sender, EventArgs e)
        {
            mnuShowSnapshots.Checked = m_settings.ShowSnapShots;
            mnuAutosizeSnapshot.Checked = m_settings.AutoSizeSnapShotPanel;
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> mnuShowSnapshots_Click event handler. </summary>
        /// <param name="sender"> Sender. </param>
        /// <param name="e">      Arguments. </param>
        ///-------------------------------------------------------------------------------------------------
        private void mnuShowSnapshots_Click(object sender, EventArgs e)
        {
            try
            {
                m_settings.ShowSnapShots = !m_settings.ShowSnapShots;

                dbObjectListPrimary.ShowSnapShots = m_settings.ShowSnapShots;
                dbObjectListSecondary.ShowSnapShots = m_settings.ShowSnapShots;
            }
            catch (Exception err)
            {
                MessageDialog(err.ToString(), MessageBoxIcon.Error);
            }
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> mnuSetSnapshotsLocation_Click event handler. </summary>
        /// <param name="sender"> Sender. </param>
        /// <param name="e">      Arguments. </param>
        ///-------------------------------------------------------------------------------------------------
        private void mnuSetSnapshotsLocation_Click(object sender, EventArgs e)
        {
            try
            {
                if (!String.IsNullOrWhiteSpace(m_settings.SnapShotDirectory))
                    folderBrowserDialog.SelectedPath = m_settings.SnapShotDirectory;

                DialogResult dr = folderBrowserDialog.ShowDialog();
                if (dr == System.Windows.Forms.DialogResult.OK)
                {
                    m_settings.SnapShotDirectory = folderBrowserDialog.SelectedPath;

                    dbObjectListPrimary.SnapShotsDirectory = m_settings.SnapShotDirectory;
                    dbObjectListSecondary.SnapShotsDirectory = m_settings.SnapShotDirectory;
                }
            }
            catch (Exception err)
            {
                MessageDialog(err.ToString(), MessageBoxIcon.Error);
            }
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Event handler. Called by dbOobjectList for selected index changed events. </summary>
        /// <param name="sender"> Source of the event. </param>
        /// <param name="e">      Event information. </param>
        ///-------------------------------------------------------------------------------------------------
        private void dbOobjectList_SelectedIndexChanged(object sender, EventArgs e)
        {
            try
            {
                // Previously loaded snap shot, unnecessary now.
            }
            catch
            { }
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> FormMain_FormClosing event handler. </summary>
        /// <param name="sender"> Sender. </param>
        /// <param name="e">      Arguments. </param>
        ///-------------------------------------------------------------------------------------------------
        private void FormMain_FormClosing(object sender, FormClosingEventArgs e)
        {
            try
            {
                m_settings.Save(AppDataMgr.GetFullSettingsFilePath());
            }
            catch
            { }
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> mnuAutosizeSnapshot_Click event handler. </summary>
        /// <param name="sender"> Sender. </param>
        /// <param name="e">      Arguments. </param>
        ///-------------------------------------------------------------------------------------------------
        private void mnuAutosizeSnapshot_Click(object sender, EventArgs e)
        {
            m_settings.AutoSizeSnapShotPanel = !m_settings.AutoSizeSnapShotPanel;
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Event handler. Called by MNU_Filter for drop down opening events. </summary>
        /// <param name="sender"> Source of the event. </param>
        /// <param name="e"> Event information. </param>
        ///-------------------------------------------------------------------------------------------------
        private void MNU_Filter_DropDownOpening(object sender, EventArgs e)
        {
            lock (m_threadHelper)
            {
                mnuNewFilter.Enabled = true;
                mnuIncludeFilter.Enabled = true;
                mnuExcludeFilter.Enabled = false; // Not implemented.
                mnuClearFilter.Enabled = m_threadHelper.Filter != null;
                mnuViewFilter.Enabled = m_threadHelper.Filter != null;
            }
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Event handler. Called by LBL_FilterImage for click events. </summary>
        /// <param name="sender"> Source of the event. </param>
        /// <param name="e"> Event information. </param>
        ///-------------------------------------------------------------------------------------------------
        private void LBL_FilterImage_Click(object sender, EventArgs e)
        {
            using (FormFilterDisplay form = new FormFilterDisplay())
            {
                lock (m_threadHelper)
                {
                    if (m_threadHelper.Filter != null)
                        form.SetFilter(m_threadHelper.Filter.BasicSQL);
                }

                form.ShowDialog();
            }
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Event handler. Called by MNU_Properties for click events. </summary>
        /// <param name="sender"> Source of the event. </param>
        /// <param name="e"> Event information. </param>
        ///-------------------------------------------------------------------------------------------------
        private void MNU_Properties_Click(object sender, EventArgs e)
        {
            using (FormProperties form = new FormProperties())
            {
                Monitor.Enter(m_database);

                try
                {
                    form.SetGameCount(m_database.GameCount);
                    form.SetDbSizeInBytes(m_database.SizeInBytes);
                    form.SetDbLastModifiedTimeUTC(m_database.LastModifiedTimeUTC);
                }
                finally
                {
                    Monitor.Exit(m_database);
                }

                form.ShowDialog();
            }
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Event handler. Called by MNU_ClearFilter for click events. </summary>
        /// <param name="sender"> Source of the event. </param>
        /// <param name="e"> Event information. </param>
        ///-------------------------------------------------------------------------------------------------
        private void MNU_ClearFilter_Click(object sender, EventArgs e)
        {
            StartNewFilterThread(null, FilterMode.Standard, ActiveObjectList);
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Event handler. Called by MNU_Filter for click events. </summary>
        /// <param name="sender"> Source of the event. </param>
        /// <param name="e">      Event information. </param>
        ///-------------------------------------------------------------------------------------------------
        private void MNU_NewFilter_Click(object sender, EventArgs e)
        {
            try
            {
                ICollection<String> categories;
                ICollection<String> controls;
                ICollection<String> regions;

                lock (m_database)
                {
                    categories = m_database.GetAvailableCategories();
                    controls = m_database.GetAvailableControls();
                    regions = m_database.GetAvailableRegions();
                }

                using (FormAdvFilter form = new FormAdvFilter(categories, controls, regions))
                    if (form.ShowDialog() == System.Windows.Forms.DialogResult.OK)
                        StartNewFilterThread(form.Filter, FilterMode.Standard, ActiveObjectList);
            }
            catch (Exception err)
            {
                MessageDialog(err.Message, MessageBoxIcon.Error);
            }
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Event handler. Called by MNU_IncludeFilter for click events. </summary>
        /// <exception cref="NotImplementedException"> Thrown when the requested operation is
        /// unimplemented. </exception>
        /// <param name="sender"> Source of the event. </param>
        /// <param name="e">      Event information. </param>
        ///-------------------------------------------------------------------------------------------------
        private void MNU_IncludeFilter_Click(object sender, EventArgs e)
        {
            try
            {
                ICollection<String> categories;
                ICollection<String> controls;
                ICollection<String> regions;

                lock (m_database)
                {
                    categories = m_database.GetAvailableCategories();
                    controls = m_database.GetAvailableControls();
                    regions = m_database.GetAvailableRegions();
                }

                using (FormAdvFilter form = new FormAdvFilter(categories, controls, regions))
                    if (form.ShowDialog() == System.Windows.Forms.DialogResult.OK)
                        StartNewFilterThread(form.Filter, FilterMode.Include, ActiveObjectList);
            }
            catch (Exception err)
            {
                MessageDialog(err.Message, MessageBoxIcon.Error);
            }
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Event handler. Called by MNU_ExcludeFilter for click events. </summary>
        /// <exception cref="NotImplementedException"> Thrown when the requested operation is
        /// unimplemented. </exception>
        /// <param name="sender"> Source of the event. </param>
        /// <param name="e">      Event information. </param>
        ///-------------------------------------------------------------------------------------------------
        private void MNU_ExcludeFilter_Click(object sender, EventArgs e)
        {
            try
            {
                throw new NotImplementedException();
            }
            catch (Exception err)
            {
                MessageDialog(err.Message, MessageBoxIcon.Error);
            }
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Event handler. Called by MNU_DatabaseCreation for click events. </summary>
        /// <param name="sender"> Source of the event. </param>
        /// <param name="e"> Event information. </param>
        ///-------------------------------------------------------------------------------------------------
        private void MNU_DatabaseCreation_Click(object sender, EventArgs e)
        {
            PromptUserForNewDatabase();
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Event handler. Called by MNU_Exit for click events. </summary>
        /// <param name="sender"> Source of the event. </param>
        /// <param name="e"> Event information. </param>
        ///-------------------------------------------------------------------------------------------------
        private void MNU_Exit_Click(object sender, EventArgs e)
        {
            this.Close();
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Event handler. Called by mnuOpenMaLaGameList for click events. </summary>
        /// <param name="sender"> Source of the event. </param>
        /// <param name="e">      Event information. </param>
        ///-------------------------------------------------------------------------------------------------
        private void mnuOpenMaLaGameList_Click(object sender, EventArgs e)
        {
            try
            {
                openFileDialog.Filter = "Mala Game List Files (*.mlg)|*.mlg|All Types (*.*)|*.*";
                if (openFileDialog.ShowDialog() == DialogResult.OK)
                {


                    BackgroundWorker openMaLaGameListWorker = new BackgroundWorker();
                    openMaLaGameListWorker.DoWork += new DoWorkEventHandler(openMaLaGameListWorker_DoWork);
                    openMaLaGameListWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(openMaLaGameListWorker_RunWorkerCompleted);
                    openMaLaGameListWorker.RunWorkerAsync(openFileDialog.FileName);
                }
            }
            catch (Exception err)
            {
                MessageDialog(err.ToString(), MessageBoxIcon.Error);
            }
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Event handler. Called by mnuSaveAsMaLaGameList for click events. </summary>
        /// <param name="sender"> Source of the event. </param>
        /// <param name="e">      Event information. </param>
        ///-------------------------------------------------------------------------------------------------
        private void mnuSaveAsMaLaGameList_Click(object sender, EventArgs e)
        {
            try
            {
                saveFileDialog.Filter = "Mala Game List Files (*.mlg)|*.mlg|All Types (*.*)|*.*";

                if (saveFileDialog.ShowDialog() == DialogResult.OK)
                    SaveMaLaGameList(saveFileDialog.FileName, ActiveObjectList);
            }
            catch (Exception err)
            {
                MessageDialog(err.ToString(), MessageBoxIcon.Error);
            }
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Event handler. Called by FormMain for shown events. </summary>
        /// <param name="sender"> Source of the event. </param>
        /// <param name="e"> Event information. </param>
        ///-------------------------------------------------------------------------------------------------
        private void FormMain_Shown(object sender, EventArgs e)
        {
            try
            {
                // Load settings.
                if (File.Exists(AppDataMgr.GetFullSettingsFilePath()))
                    m_settings.Load(AppDataMgr.GetFullSettingsFilePath());

                dbObjectListPrimary.AssignSnapShotProperties(m_settings.ShowSnapShots, m_settings.AutoSizeSnapShotPanel, m_settings.SnapShotDirectory);
                dbObjectListSecondary.AssignSnapShotProperties(m_settings.ShowSnapShots, m_settings.AutoSizeSnapShotPanel, m_settings.SnapShotDirectory);

                try
                {
                    lock (m_database)
                    {
                        switch (m_database.Connect(AppDataMgr.GetFullDatbaseFilePath()))
                        {
                            case ConnectionStatus.RequiresRefresh:
                                MessageDialog("Schema has changed. You will now be prompted to re-build the database.", MessageBoxIcon.Information);
                                PromptUserForNewDatabase();
                                break;

                            case ConnectionStatus.New:
                                MessageDialog("No database found. You will now be prompted to build it.", MessageBoxIcon.Information);
                                PromptUserForNewDatabase();
                                break;

                            case ConnectionStatus.Success:
                                StartNewFilterThread(null, FilterMode.Standard, dbObjectListPrimary);
                                break;

                            default:
                                throw new NotImplementedException();
                        } // End switch statement.
                    }
                }
                catch (Exception err)
                {
                    MessageDialog(String.Format("Unable to open existing games database: {0}\n\n You will now be prompted to (re)create it.", err.Message), MessageBoxIcon.Error);
                    PromptUserForNewDatabase();
                }
            }
            catch (Exception err)
            {
                MessageDialog(err.ToString(), MessageBoxIcon.Error);
            }
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Event handler. Called by mnuCopyToOtherList for click events. </summary>
        /// <param name="sender"> Source of the event. </param>
        /// <param name="e">      Event information. </param>
        ///-------------------------------------------------------------------------------------------------
        private void mnuCopyToOtherList_Click(object sender, EventArgs e)
        {
            try
            {
                if (ActiveObjectList.SelectedItemCount == 1)
                {
                    DbObjectList targetList;
                    if (ActiveObjectList == dbObjectListPrimary)
                        targetList = dbObjectListSecondary;
                    else
                        targetList = dbObjectListPrimary;

                    DatabaseFilter filter = new DatabaseFilter();
                    filter.AddNameFilter(ActiveObjectList.SelectedGameName, Condition.Equal);

                    StartNewFilterThread(filter, FilterMode.Include, targetList);
                }
            }
            catch (Exception err)
            {
                MessageDialog(err.Message, MessageBoxIcon.Error);
            }
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Delete selected items. </summary>
        /// <param name="sender"> Sender. </param>
        /// <param name="e">      Arguments. </param>
        ///-------------------------------------------------------------------------------------------------
        private void mnuDelete_Click(object sender, EventArgs e)
        {
            DeleteSelected();
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Undo last delete. </summary>
        /// <param name="sender"> Sender. </param>
        /// <param name="e">      Arguments. </param>
        ///-------------------------------------------------------------------------------------------------
        private void mnuUndo_Click(object sender, EventArgs e)
        {
            try
            {
                ActiveObjectList.Undo();
            }
            catch (Exception err)
            {
                MessageDialog(err.Message, MessageBoxIcon.Error);
            }
        }

        #endregion

        // --------------------------------------------------------------------
        // Helper Methods
        // --------------------------------------------------------------------
        #region Helper Methods

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Gets the active object list. </summary>
        /// <value> A List of active objects. </value>
        ///-------------------------------------------------------------------------------------------------
        private DbObjectList ActiveObjectList
        {
            get
            {
                if (dbObjectListSecondary.ContainsFocus)
                    return dbObjectListSecondary;
                else
                    return dbObjectListPrimary;
            }
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Creates a new list xml file using supplied mame executable. </summary>
        /// <exception cref="Exception"> Thrown when mame returns error/invalid exit code. </exception>
        /// <param name="mameExecutable"> The mame executable. </param>
        /// <returns> File name of new list xml file. </returns>
        ///-------------------------------------------------------------------------------------------------
        private String CreateNewListXMLFile(String mameExecutable)
        {
            String tempFile = AppDataMgr.GetNewTempListXMLFilePath();

            Process p = new Process();
            p.StartInfo.FileName = mameExecutable;
            p.StartInfo.Arguments = "-listxml";
            p.StartInfo.UseShellExecute = false;
            p.StartInfo.RedirectStandardOutput = true;
            p.StartInfo.CreateNoWindow = true;

            p.Start();

            using (StreamWriter sw = new StreamWriter(tempFile))
            {
                sw.Write(p.StandardOutput.ReadToEnd());
                sw.Close();
            }

            p.WaitForExit();

            if (p.ExitCode != 0)
                throw new Exception(String.Format("Process returned invalid exit code({0}).", p.ExitCode));
            else
                return (tempFile);
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Prompt user for new database. </summary>
        ///-------------------------------------------------------------------------------------------------
        private void PromptUserForNewDatabase()
        {
            using (FormDatabaseCreation form = new FormDatabaseCreation())
            {
                if (form.ShowDialog() == DialogResult.OK)
                {
                    String sourceFile = form.GetSourceFile();
                    String catVerFile = form.GetCatVerFile();

                    StartNewDatabaseThread(sourceFile, catVerFile);
                }
            }
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Starts a thread used to generate a new database. </summary>
        /// <exception cref="ArgumentNullException"> Thrown when one or more required arguments are null. </exception>
        /// <param name="listXmlFile"> The list xml file. </param>
        /// <param name="catVerFile">  The category ver file. </param>
        ///-------------------------------------------------------------------------------------------------
        private void StartNewDatabaseThread(String listXmlFile, String catVerFile)
        {
            if (listXmlFile == null)
                throw new ArgumentNullException("listXmlFile");

            if (catVerFile == null)
                throw new ArgumentNullException("catVerFile");

            Monitor.Enter(m_threadHelper);

            try
            {
                m_threadHelper.ListXMLFile = listXmlFile;
                m_threadHelper.CatVerFile = catVerFile;
            }
            finally
            {
                Monitor.Exit(m_threadHelper);
            }

            BackgroundWorker worker = new BackgroundWorker();
            worker.DoWork += new DoWorkEventHandler(DoCreateNewDatabase);
            worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(DoCreateNewDatabaseComplete);
            worker.RunWorkerAsync();
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Executes the create new database operation. </summary>
        /// <param name="sender"> Source of the event. </param>
        /// <param name="e">      Do work event information. </param>
        ///-------------------------------------------------------------------------------------------------
        private void DoCreateNewDatabase(Object sender, DoWorkEventArgs e)
        {
            lock (m_threadHelper)
            {
                lock (m_database)
                {
                    if (String.Compare(Path.GetExtension(m_threadHelper.ListXMLFile), ".exe", true) == 0)
                    {
                        UpdateStatus(AppStatus.CreatingListXML);
                        m_threadHelper.ListXMLFile = CreateNewListXMLFile(m_threadHelper.ListXMLFile);
                    }

                    CatVerList catVerList = null;
                    if (!String.IsNullOrWhiteSpace(m_threadHelper.CatVerFile))
                    {
                        UpdateStatus(AppStatus.ReadingCatVer);
                        catVerList = new CatVerList(m_threadHelper.CatVerFile);
                    }

                    UpdateStatus(AppStatus.BuildingDb);

                    m_database.Clear();
                    m_database.Load(m_threadHelper.ListXMLFile, catVerList, true);
                }
            }
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Executes the create new database complete operation. </summary>
        /// <param name="sender"> Source of the event. </param>
        /// <param name="e">      Run worker completed event information. </param>
        ///-------------------------------------------------------------------------------------------------
        private void DoCreateNewDatabaseComplete(Object sender, RunWorkerCompletedEventArgs e)
        {
            try
            {
                if (!e.Cancelled)
                {
                    if (e.Error != null)
                    {
                        UpdateStatus(AppStatus.Idle);
                        MessageDialog(e.Error.ToString(), MessageBoxIcon.Error);
                    }
                    else
                    {
                        dbObjectListSecondary.Clear();
                        StartNewFilterThread(null, FilterMode.Standard, dbObjectListPrimary);
                    }
                }
                else
                    UpdateStatus(AppStatus.Idle);
            }
            catch (Exception err)
            {
                UpdateStatus(AppStatus.Idle);
                MessageDialog(err.ToString(), MessageBoxIcon.Error);
            }
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Starts a thread used to run a filtered query against the database. </summary>
        /// <param name="filter">       The filter. </param>
        /// <param name="mode">         The mode. </param>
        /// <param name="dbObjectList"> Active object list. </param>
        ///-------------------------------------------------------------------------------------------------
        private void StartNewFilterThread(DatabaseFilter filter, FilterMode mode, DbObjectList dbObjectList)
        {
            lock (m_threadHelper)
                m_threadHelper.Filter = filter;

            BackgroundWorker worker = new BackgroundWorker();
            worker.DoWork += new DoWorkEventHandler(DoFilterDatabase);
            worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(DoFilterDatabaseComplete);
            worker.RunWorkerAsync(new Object[] { dbObjectList, mode });
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Executes the filter database operation. </summary>
        /// <param name="sender"> Source of the event. </param>
        /// <param name="e">      Do work event information. </param>
        ///-------------------------------------------------------------------------------------------------
        private void DoFilterDatabase(Object sender, DoWorkEventArgs e)
        {
            UpdateStatus(AppStatus.SQL);

            try
            {
                lock (m_threadHelper)
                    lock (m_database)
                        m_threadHelper.Items = m_database.GetDbItems(m_threadHelper.Filter);

                e.Result = e.Argument; // Pass arguments (object list and filter mode) into completion handler.
            }
            finally
            {
                UpdateStatus(AppStatus.Idle);
            }
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Executes the filter database complete operation. </summary>
        /// <param name="sender"> Source of the event. </param>
        /// <param name="e">      Run worker completed event information. </param>
        ///-------------------------------------------------------------------------------------------------
        private void DoFilterDatabaseComplete(Object sender, RunWorkerCompletedEventArgs e)
        {
            try
            {
                if (!e.Cancelled)
                {
                    if (e.Error != null)
                    {
                        UpdateStatus(AppStatus.Idle);
                        MessageDialog(e.Error.ToString(), MessageBoxIcon.Error);
                    }
                    else
                    {
                        DbObjectList dbObjectList = (DbObjectList)((Object[])e.Result)[0];
                        FilterMode filterMode = (FilterMode)((Object[])e.Result)[1];

                        FilterObjectList(filterMode, dbObjectList);
                    }
                }
                else
                    UpdateStatus(AppStatus.Idle);
            }
            catch (Exception err)
            {
                UpdateStatus(AppStatus.Idle);
                MessageDialog(err.ToString(), MessageBoxIcon.Error);
            }
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Filters the game list. </summary>
        /// <exception cref="NotImplementedException"> Thrown when the requested operation is
        /// unimplemented. </exception>
        /// <param name="mode">         The mode. </param>
        /// <param name="dbObjectList"> Active object list. </param>
        ///-------------------------------------------------------------------------------------------------
        private void FilterObjectList(FilterMode mode, DbObjectList dbObjectList)
        {
            if (this.InvokeRequired)
                this.Invoke(new MethodInvoker(delegate { FilterObjectList(mode, dbObjectList); }));
            else
            {
                try
                {
                    UpdateStatus(AppStatus.Populating);

                    try
                    {
                        if (dbObjectList == null)
                            throw new ArgumentNullException("dbObjectList");

                        lock (m_threadHelper)
                        {
                            SetFilterImage(m_threadHelper.Filter);

                            if (mode == FilterMode.Standard)
                                dbObjectList.ClearAndAdd(m_threadHelper.Items);
                            else if (mode == FilterMode.Include)
                                dbObjectList.Add(m_threadHelper.Items);
                            else
                                throw new NotImplementedException(String.Format("Mode [{0}] not implemented.", mode.ToString()));
                        }

                        UpdateStatusBarWithCount();
                    }
                    finally
                    {
                        UpdateStatus(AppStatus.Idle);
                    }
                }
                catch (Exception e)
                {
                    MessageDialog(e.ToString(), MessageBoxIcon.Error);
                }
            }
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Updates the application status. </summary>
        /// <exception cref="Exception"> Thrown when . </exception>
        /// <param name="status"> The status. </param>
        ///-------------------------------------------------------------------------------------------------
        private void UpdateStatus(AppStatus status)
        {
            switch (status)
            {
                case AppStatus.Idle:
                    UpdateStatusBar("");
                    UpdateCursor(Cursors.Default);
                    break;

                case AppStatus.Loading:
                    UpdateStatusBar("Loading...");
                    UpdateCursor(Cursors.WaitCursor);
                    break;

                case AppStatus.Saving:
                    UpdateStatusBar("Saving...");
                    UpdateCursor(Cursors.WaitCursor);
                    break;

                case AppStatus.CreatingListXML:
                    UpdateStatusBar("Creating list xml...");
                    UpdateCursor(Cursors.WaitCursor);
                    break;

                case AppStatus.BuildingDb:
                    UpdateStatusBar("Building database...");
                    UpdateCursor(Cursors.WaitCursor);
                    break;

                case AppStatus.ReadingCatVer:
                    UpdateStatusBar("Reading category/version info...");
                    UpdateCursor(Cursors.WaitCursor);
                    break;

                case AppStatus.SQL:
                    UpdateStatusBar("Waiting on database...");
                    UpdateCursor(Cursors.WaitCursor);
                    break;

                case AppStatus.Populating:
                    UpdateStatusBar("Filling list...");
                    UpdateCursor(Cursors.WaitCursor);
                    break;

                default:
                    throw new Exception("Invalid/unknown status: " + status.ToString());
            } // End switch statement.
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Updates the status bar with specified message. </summary>
        /// <param name="message"> The message. </param>
        ///-------------------------------------------------------------------------------------------------
        private void UpdateStatusBar(String message)
        {
            if (statusStrip.InvokeRequired)
                statusStrip.BeginInvoke(new MethodInvoker(delegate { UpdateStatusBar(message); }));
            else
            {
                if (String.IsNullOrEmpty(message))
                    LBL_StatusMessage.Text = "";
                else
                    LBL_StatusMessage.Text = message;
            }
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Updates the status bar with game count. </summary>
        ///-------------------------------------------------------------------------------------------------
        private void UpdateStatusBarWithCount()
        {
            if (statusStrip.InvokeRequired)
                statusStrip.BeginInvoke(new MethodInvoker(delegate { UpdateStatusBarWithCount(); }));
            else
                LBL_GameCountValue.Text = String.Format(EditorConst.GAME_COUNT_LABEL_TEXT, ActiveObjectList.Count);
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Updates the cursor. </summary>
        /// <param name="cursor"> Cursor. </param>
        ///-------------------------------------------------------------------------------------------------
        private void UpdateCursor(System.Windows.Forms.Cursor cursor)
        {
            if (this.InvokeRequired)
                this.BeginInvoke(new MethodInvoker(delegate { UpdateCursor(cursor); }));
            else
                this.Cursor = cursor;
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Message dialog. </summary>
        /// <param name="message"> The message. </param>
        /// <param name="icon"> The icon. </param>
        ///-------------------------------------------------------------------------------------------------
        private void MessageDialog(String message, MessageBoxIcon icon)
        {
            if (this.InvokeRequired)
                this.BeginInvoke(new MethodInvoker(delegate { MessageDialog(message, icon); }));
            else
                MessageBox.Show(message, this.Text, MessageBoxButtons.OK, icon);
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Deletes selected items. </summary>
        ///-------------------------------------------------------------------------------------------------
        private void DeleteSelected()
        {
            if (this.InvokeRequired)
                this.Invoke(new MethodInvoker(delegate { DeleteSelected(); }));
            else
            {
                try
                {
                    ActiveObjectList.DeleteSelected();
                    UpdateStatusBarWithCount();
                }
                catch (Exception err)
                {
                    MessageDialog(err.Message, MessageBoxIcon.Error);
                }
            }
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Sets filter. </summary>
        /// <param name="filter"> The filter. </param>
        ///-------------------------------------------------------------------------------------------------
        private void SetFilterImage(DatabaseFilter filter)
        {
            if (statusStrip.InvokeRequired)
                statusStrip.BeginInvoke(new MethodInvoker(delegate { SetFilterImage(filter); }));
            else
                LBL_FilterImage.Visible = (filter != null);
        }

        #endregion

        #region MaLa

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Saves a MaLa game list. </summary>
        /// <exception cref="ArgumentNullException"> Thrown when one or more required arguments are null. </exception>
        /// <exception cref="Exception">             Thrown when an exception error condition occurs. </exception>
        /// <param name="filePath">     Full pathname of the file. </param>
        /// <param name="dbObjectList"> Active object list. </param>
        ///-------------------------------------------------------------------------------------------------
        private void SaveMaLaGameList(String filePath, DbObjectList dbObjectList)
        {
            if (this.InvokeRequired)
                this.Invoke(new MethodInvoker(delegate { SaveMaLaGameList(filePath, dbObjectList); }));
            else
            {
                try
                {
                    UpdateStatus(AppStatus.Saving);

                    if (dbObjectList == null)
                        throw new ArgumentNullException("dbObjectList");

                    lock (m_database)
                    {
                        if (filePath == null)
                            throw new ArgumentNullException("filePath");

                        MalaGameList gamelist = new MalaGameList();

                        gamelist.Name = Path.GetFileNameWithoutExtension(filePath);
                        gamelist.Author = "GameListEditor";
                        gamelist.Comment = "Generated by GameListEditor.";

                        if (File.Exists(filePath))
                        {
                            gamelist.Creation = File.GetCreationTime(filePath);
                            gamelist.Modification = DateTime.Now;

                            File.Delete(filePath);
                        }
                        else
                        {
                            gamelist.Creation = DateTime.Now;
                            gamelist.Modification = gamelist.Creation;
                        }

                        using (SQLiteCommand command = m_database.Connection.CreateCommand())
                        {
                            command.CommandTimeout = 0;

                            command.CommandText = String.Format("select a.[{0}], a.[{1}], a.[{2}], b.[{4}], b.[{5}], a.[{6}], a.[{7}], a.[{8}], a.[{9}], a.[{10}], b.[{11}], c.[{11}], case when b.[{3}] = 0 then 'horizontal' else 'vertical' end from [{12}] a left join [{13}] b using ([{14}]) left join [{15}] c using ([{14}]) where a.[{14}] = ?",
                                SqlConst.ROM_OF_COL, SqlConst.SOURCE_FILE_COL, SqlConst.INPUT_COINS_COL, SqlConst.ROTATE_COL,
                                SqlConst.WIDTH_COL, SqlConst.HEIGHT_COL, SqlConst.DRIVER_STATUS_COL, SqlConst.DRIVER_COLOR_COL,
                                SqlConst.DRIVER_SOUND_COL, SqlConst.DRIVER_EMULATION_COL, SqlConst.DRIVER_GRAPHIC_COL,
                                SqlConst.TYPE_COL, SqlConst.GAME_TABLE, SqlConst.DISPLAY_TABLE, SqlConst.GAME_NAME_COL,
                                SqlConst.CONTROL_TABLE);

                            command.Parameters.Add(command.CreateParameter());
                            command.Prepare();

                            foreach (ListViewItem item in dbObjectList.ListViewItems)
                            {
                                command.Parameters[0].Value = item.SubItems[EditorConst.NAME_COLUMN_INDEX].Text;
                                using (SQLiteDataReader reader = command.ExecuteReader())
                                {
                                    if (!reader.Read())
                                        throw new Exception(String.Format("Game [{0}] not found.", item.SubItems[EditorConst.NAME_COLUMN_INDEX].Text));

                                    MalaGame game = new MalaGame();

                                    game.Name = item.SubItems[EditorConst.NAME_COLUMN_INDEX].Text;
                                    game.RomOf = reader.IsDBNull(0) ? "" : reader.GetString(0);
                                    game.CloneOf = item.SubItems[EditorConst.CLONE_OF_COLUMN_INDEX].Text;
                                    game.Sourcefile = reader.GetString(1);

                                    // Use custom description if defined, otherwise use default description.
                                    if (String.IsNullOrWhiteSpace(item.SubItems[EditorConst.CUSTOM_DESCRIPTION_COLUMN_INDEX].Text))
                                        game.Description = item.SubItems[EditorConst.DESCRIPTION_COLUMN_INDEX].Text;
                                    else
                                        game.Description = item.SubItems[EditorConst.CUSTOM_DESCRIPTION_COLUMN_INDEX].Text;

                                    Int16 year;
                                    if (Int16.TryParse(item.SubItems[EditorConst.YEAR_COLUMN_INDEX].Text, out year))
                                        game.Year = year;
                                    else
                                        game.Year = 0;

                                    game.Manufacturer = item.SubItems[EditorConst.MANUFACTURER_COLUMN_INDEX].Text;
                                    game.Genre = item.SubItems[EditorConst.CATEGORY_COLUMN_INDEX].Text;

                                    Int16 inputButtons;
                                    if (Int16.TryParse(item.SubItems[EditorConst.BUTTON_COUNT_COLUMN_INDEX].Text, out inputButtons))
                                        game.InputButtons = inputButtons;
                                    else
                                        game.InputButtons = 0;

                                    game.InputCoins = reader.IsDBNull(2) ? (Int16)0 : Convert.ToInt16(reader.GetValue(2));
                                    game.InputPlayers = item.SubItems[EditorConst.PLAYER_COUNT_COLUMN_INDEX].Text;
                                    game.InputControl = reader.IsDBNull(11) ? "" : reader.GetString(11);

                                    game.VideoScreen = reader.IsDBNull(10) ? "" : ((DisplayType)reader.GetInt16(10)).ToString();
                                    game.VideoOrientation = reader.GetString(12);
                                    game.VideoWidth = reader.IsDBNull(3) ? (Int16)0 : Convert.ToInt16(reader.GetValue(3));
                                    game.VideoHeight = reader.IsDBNull(4) ? (Int16)0 : Convert.ToInt16(reader.GetValue(4));

                                    game.DriverStatus = reader.IsDBNull(5) ? DriverAttributeStatus.preliminary.ToString() : ((DriverAttributeStatus)reader.GetInt16(5)).ToString();
                                    game.DriverColor = reader.IsDBNull(6) ? DriverAttributeStatus.preliminary.ToString() : ((DriverAttributeStatus)reader.GetInt16(6)).ToString();
                                    game.DriverSound = reader.IsDBNull(7) ? DriverAttributeStatus.preliminary.ToString() : ((DriverAttributeStatus)reader.GetInt16(7)).ToString();
                                    game.DriverEmulation = reader.IsDBNull(8) ? DriverAttributeStatus.preliminary.ToString() : ((DriverAttributeStatus)reader.GetInt16(8)).ToString();
                                    game.DriverGraphic = reader.IsDBNull(9) ? DriverAttributeStatus.preliminary.ToString() : ((DriverAttributeStatus)reader.GetInt16(9)).ToString();

                                    // Controls, Joy, Buttons, Details?????

                                    // RomPath ?????
                                    // Extension ?????

                                    game.Played = 0;

                                    gamelist.Games.Add(game);
                                } // End using reader.
                            } // End for loop. 
                        } // End using command.

                        gamelist.Save(filePath);

                        // Success
                        MessageDialog(String.Format("Saved [{0}] game(s).", gamelist.Games.Count.ToString("#,##0")),
                            MessageBoxIcon.Information);
                    }
                }
                catch (Exception err)
                {
                    MessageDialog(err.ToString(), MessageBoxIcon.Error);
                }
                finally
                {
                    UpdateStatus(AppStatus.Idle);
                }
            }
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Event handler. Called by openMaLaGameListWorker for do work events. </summary>
        /// <exception cref="ArgumentNullException"> Thrown when one or more required arguments are null. </exception>
        /// <exception cref="Exception">             Thrown when an exception error condition occurs. </exception>
        /// <param name="sender"> Source of the event. </param>
        /// <param name="e">      Do work event information. </param>
        ///-------------------------------------------------------------------------------------------------
        private void openMaLaGameListWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            UpdateStatus(AppStatus.Loading);

            lock (m_database)
            {
                String filePath = (e.Argument == null) ? null : e.Argument as String;
                if (filePath == null)
                    throw new ArgumentNullException("filePath");

                MalaGameList gamelist = new MalaGameList();
                gamelist.Load(filePath);

                Dictionary<String, DbItem> completeList = new Dictionary<string, DbItem>(StringComparer.OrdinalIgnoreCase);
                foreach (DbItem item in m_database.GetDbItems(null))
                    completeList.Add(item.Name, item);

                List<DbItem> filteredList = new List<DbItem>();
                foreach (MalaGame game in gamelist.Games)
                {
                    if (completeList.ContainsKey(game.Name))
                    {
                        // Update custom description if defined.
                        if ((game.Description != null) && !game.Description.Equals(completeList[game.Name].Description))
                            completeList[game.Name].CustomDescription = game.Description;

                        // Add to list.
                        filteredList.Add(completeList[game.Name]);
                    }
                }

                lock (m_threadHelper)
                    m_threadHelper.Items = filteredList;
            }
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Event handler. Called by openMaLaGameListWorker for run worker completed events. </summary>
        /// <param name="sender"> Source of the event. </param>
        /// <param name="e">      Run worker completed event information. </param>
        ///-------------------------------------------------------------------------------------------------
        private void openMaLaGameListWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            try
            {
                RunWorkerCompletedEventArgs args = new RunWorkerCompletedEventArgs(new Object[] { ActiveObjectList, FilterMode.Standard },
                    e.Error, e.Cancelled);

                DoFilterDatabaseComplete(sender, args);
            }
            catch (Exception err)
            {
                MessageDialog(err.Message, MessageBoxIcon.Error);
            }
        }

        ///-------------------------------------------------------------------------------------------------
        /// <summary> Event handler. Called by dbObjectList for click events. </summary>
        /// <param name="sender"> Source of the event. </param>
        /// <param name="e">      Event information. </param>
        ///-------------------------------------------------------------------------------------------------
        private void dbObjectList_Click(object sender, EventArgs e)
        {
            UpdateStatusBarWithCount();
        }

        #endregion
    }
}
